/*
*	Developed by Leonardo Chatain and Pedro Dusso, at Universidade Federal do Rio Grande do Sul, Brasil, 2009/1
*	Source available at glpacman.codeplex.com
*/

#include "Game.h"

Game::Game(const char* path) {
	level = 1;
	maze.openMaze(path);
	nPhantoms = maze.getNPhantoms();
	pac.setPosition(maze.getPacStart());
	phantoms = new Phantom[nPhantoms]();
	for (int i = 0; i < nPhantoms; i++) {
		phantoms[i].setPosition(maze.getPhantomStart()[i]);
	}


	// Cameras inicialization
	// Above camera
	aboveCamera.setAngle(42);	
	aboveCamera.setUp(Vector3D(0, 0, -1));
	aboveCamera.setPosition(maze.getColumns()*CELL_SIZE/2, (level==1)?(300):(400), (maze.getLines()*CELL_SIZE/2)*1.1);
	aboveCamera.setLookAt(maze.getColumns()*CELL_SIZE/2,
		0,
		maze.getLines()*CELL_SIZE/2);

	// fpCamera
	fpCamera.setAngle(45);
	fpCamera.setUp(Vector3D(0, 1, 0));

	// tpCamera
	tpCamera.setAngle(45);
	tpCamera.setUp(Vector3D(0,1,0));

	setActiveCamera(ABOVE_CAMERA);
	paused = false;
	PlaySound(TEXT("prep.wav"), NULL, SND_SYNC);
}

Game::~Game(void)
{
}

void Game::resetAboveCamera() {
		// Cameras inicialization
	// Above camera
	aboveCamera.setAngle(42);	
	aboveCamera.setUp(Vector3D(0, 0, -1));
	aboveCamera.setPosition(maze.getColumns()*CELL_SIZE/2, (level==1)?(300):(400), (maze.getLines()*CELL_SIZE/2)*1.1);
	aboveCamera.setLookAt(maze.getColumns()*CELL_SIZE/2,
		0,
		maze.getLines()*CELL_SIZE/2);
}

void Game::phantomFunction() {
	srand(clock());
	for(int i = 0; i < nPhantoms; i++) {
		if (!phantoms[i].isZombie()) {

			//dir is or 1 or 2
			int dir = rand() % 2;
			dir = (dir == 0)?(1):(3);
			if (!phantoms[i].isTurning())  {
				Character::Directions left = (Character::Directions)(((int)phantoms[i].getDiscreteDirection() + 1)%4);
				Character::Directions right = (Character::Directions)(((int)phantoms[i].getDiscreteDirection() + 3)%4);
				if (!canWalk(&phantoms[i]) &&
					!canMove(&phantoms[i], left) &&
					!canMove(&phantoms[i], right))
					// if the phantom can't walk neither forward nor to any side, turn back
					phantoms[i].setIncomingDirection((Character::Directions)(((int)phantoms[i].getDiscreteDirection() + 2)%4));
				else
					//if the phantom can walk forward, he can only turn left or right
					phantoms[i].setIncomingDirection((Character::Directions)(((int)phantoms[i].getDiscreteDirection() + dir)%4));
			}
		}
		// if the phantom is zombie
		else {
			// select the phantom start position closer to him
			// select the possible direction that better aproximates the direction to go 'home'
			phantoms[i].setPosition(maze.getPhantomStart()[i]);
		}
		if (phantoms[i].getPosition().getDistanceTo(maze.getPhantomStart()[i]) <= 10)
			if (phantoms[i].isZombie())
				phantoms[i].unsetZombie();
	}
}

// Uptates the game state, including characters positions an cameras
void Game::update() {

	if (maze.getLines() == 0 || maze.getColumns() == 0) {
		cout << "The maze is not loaded" << endl;
		return;
	}
	// Phantom AI
	phantomFunction();
	pac.updateSuper();

	// Pacman movement
	if (canMove(&pac, pac.getIncomingDirection())) {
		if (currentCamera == ABOVE_CAMERA)
			pac.setDiscreteDirection(pac.getIncomingDirection());
		else
			pac.setTurningDirection(pac.getIncomingDirection());
	}
	if(canWalk(&pac) || pac.isTurning())
		pac.walk();

	// Phantom movement
	for(int i = 0; i < nPhantoms; i++) {
		if(canMove(&phantoms[i], phantoms[i].getIncomingDirection())) {
			if (currentCamera == ABOVE_CAMERA)
				phantoms[i].setDiscreteDirection(phantoms[i].getIncomingDirection());
			else
				phantoms[i].setDiscreteDirection(phantoms[i].getIncomingDirection());
		}
		if(canWalk(&phantoms[i]) || phantoms[i].isTurning())
			phantoms[i].walk();
	}

	// Pills
	int charColumn = (int)(pac.getX() / CELL_SIZE);
	int charLine = (int)(pac.getY() / CELL_SIZE);


	if (maze.getMaze()[charLine][charColumn] == PILL) {
		pac.setScore(pac.getScore() + 1);
		if (!pac.isSuper())
			PlaySound("coin.wav", NULL, SND_ASYNC);
		maze.setMazePos(EMPTY, charLine, charColumn);
		maze.decrementNPills();
	}
	if (maze.getMaze()[charLine][charColumn] == SUPER_PILL) {
		pac.setScore(pac.getScore() + SUPER_PILL_SCORE);
		PlaySound(TEXT("powerup.wav"), NULL, SND_ASYNC);
		maze.setMazePos(EMPTY, charLine, charColumn);
		maze.decrementNPills();
		pac.setSuper();
		// now we must dasativate the phantom imunity
		for (int i = 0; i < nPhantoms; i++)
			phantoms[i].setImuneToPac(false);
	}
	if (maze.getNPills() == 0)
		victory();

	//phantoms speed
	for (int i = 0; i < nPhantoms; i++) {
		if (pac.isSuper() && !phantoms[i].getImuneToPac()) {
			phantoms[i].setLinearSpeed(0.5);
		}
		else
			phantoms[i].setLinearSpeed(1);
	}

	//re-initializes phantons ate couting.
	if(!pac.isSuper())
		pac.setPhantomsAte(0);

	// Phantom collision
	for (int p = 0; p < nPhantoms; p++) {
		if (pac.getPosition().getDistanceTo(phantoms[p].getPosition()) < 1.6*CHAR_RADIUS) {
			// collided with a phantom
			if (pac.isSuper()) {
				if (!phantoms[p].isZombie() && !phantoms[p].getImuneToPac()) {
					// eat the phantom
					phantoms[p].setZombie();
					pac.increasePhantomsAte();
					//increses the pontuation received for multiple phantons ate.
					pac.setScore(pac.getScore() + (PHANTOM_SCORE*pac.getPhantomsAte())); 
					killPhantonSounds(pac.getPhantomsAte());
				}
				if (phantoms[p].getImuneToPac()) {
					// die
					die();
				}
			}
			else {
				if (!phantoms[p].isZombie()) {
					// die
					die();
				}
			}
		}
	}

	// Cameras
	// fpCamera
	const int height = 20;
	const int dist = 30;
	if (currentCamera == FPCAMERA) {
		fpCamera.setPosition(pac.getX(), pac.getHeigthPosition()+CHAR_RADIUS + 1, pac.getY());
		fpCamera.setLookAt(Vector3D(pac.getDirection().getX()*10, -2, pac.getDirection().getY()*10));
	}
	// tpCamera
	else if (currentCamera == TPCAMERA) {
		float newX = pac.getX() - pac.getDirection().getX()*dist;
		float newY = height;
		float newZ = pac.getY() - pac.getDirection().getY()*dist;
		tpCamera.setPosition(newX, newY, newZ);
		tpCamera.setLookAt(pac.getX(), CELL_SIZE, pac.getY());
	}

}

void Game::openMaze(const char* path) {
	maze.openMaze(path);
}

void Game::draw() {
	glClear(GL_COLOR_BUFFER_BIT |GL_DEPTH_BUFFER_BIT);
	glViewport(0, STATUS_MENU_HEIGHT, glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT)-STATUS_MENU_HEIGHT);
	activeCamera->view();
	maze.draw();
	pac.draw();
	for (int i = 0; i < nPhantoms; i++)
		phantoms[i].draw(pac.isSuper());

	// draw minimap
	if (currentCamera != ABOVE_CAMERA) {
		glClear(GL_DEPTH_BUFFER_BIT);
		glViewport(glutGet(GLUT_WINDOW_WIDTH)-400, glutGet(GLUT_WINDOW_HEIGHT)-300 , 400, 300);
		aboveCamera.view();
		maze.draw();
		pac.draw();
		for (int i = 0; i < nPhantoms; i++)
			phantoms[i].draw(pac.isSuper());
	}

	glClear(GL_DEPTH_BUFFER_BIT);
	glViewport(0,0,glutGet(GLUT_WINDOW_WIDTH), STATUS_MENU_HEIGHT);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluOrtho2D(0,600,0,50);

	glMatrixMode(GL_MODELVIEW);

	drawMenu();

	glutSwapBuffers();
}

void Game::drawText(int x, int y, char* string)
{
	int i, len;

	glColor3f(YELLOW);

	glLoadIdentity();
	//glTranslatef(0.0f, 0.0f, 5.0f);
	glRasterPos2i(x,y);

	glDisable(GL_TEXTURE);
	glDisable(GL_TEXTURE_2D);
	for (i = 0, len = strlen(string); i < len; i++)
	{
		glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18,(int)string[i]);
	}
	glEnable(GL_TEXTURE);
	glEnable(GL_TEXTURE_2D);
}

void Game::drawMenu(void){

	char menu[200];
	char lives[10];
	char score[10];
	char pill[10];

	itoa(pac.getLives(),lives,10);
	itoa(pac.getScore(),score,10);
	itoa(maze.getNPills(),pill,10);

	strcpy (menu,"Lives: ");
	strcat (menu,lives);
	strcat (menu,"     Points: ");
	strcat (menu,score);
	strcat (menu,"     Still ");
	strcat (menu,pill);
	strcat (menu," pills in the maze");
	
	if(pac.isSuper())
	{
		switch (pac.getPhantomsAte()) {
		case 0:
			strcat (menu," ");
		case 1:
			strcat (menu,"                                 Very good, boy!");
			break;
		case 2:
			strcat (menu,"                                 Double!!");
			break;
		case 3:
			strcat (menu,"                                 Are you insane?!");
			break;
		case 4:
			strcat (menu,"                                 YOU ARE A MOOOOONSTER...");
			break;
		}
	}

	drawText(10,25,menu);
}

// Determines if a character can walk forward
bool Game::canWalk(Character *c) {
	return canMove(c, c->getDiscreteDirection());
}

// Determines if a character can move in a direction
bool Game::canMove(Character *c, Character::Directions dir) {
	const float TRESH  =(CELL_SIZE - (CHAR_RADIUS*2)) / 2;

	if (maze.getLines() == 0 || maze.getColumns() == 0) {
		cout << "The maze is not loaded" << endl;
		return false;
	}
	int charColumn = (int)(c->getX() / CELL_SIZE);
	int charLine = (int)(c->getY() / CELL_SIZE);

	float insideDelta;
	Vector2D dirVec;
	switch (dir) {
		case (int)(Character::up):
			dirVec.setXY(UP);
			insideDelta = c->getX() - (CELL_SIZE * charColumn);
			break;
		case (int)(Character::down):
			dirVec.setXY(DOWN);
			insideDelta = c->getX() - (CELL_SIZE * charColumn);
			break;
		case (int)(Character::right):
			dirVec.setXY(RIGHT);
			insideDelta = c->getY() - (CELL_SIZE * charLine);
			break;
		case (int)(Character::left):
			dirVec.setXY(LEFT);
			insideDelta = c->getY() - (CELL_SIZE * charLine);
			break;
	}
	if (fabs(insideDelta - CELL_SIZE/2) < TRESH) {
		Point2D futurePos(dirVec.getX() * c->getLinearSpeed() + c->getX(), dirVec.getY() * c->getLinearSpeed() + c->getY());
		int nextColumn = (int)((futurePos.getX() + dirVec.getX() * CHAR_RADIUS)/CELL_SIZE);
		int nextLine = (int)((futurePos.getY() + dirVec.getY() * CHAR_RADIUS)/CELL_SIZE);
		if (maze.getMaze()[nextLine][nextColumn] != WALL)
			return true;
		else
			return false;
	}
	else
		return false;
}

void Game::keyDirection(Character::Directions direction) {
	if (currentCamera == ABOVE_CAMERA) {
		pac.setIncomingDirection(direction);
	}
	else if(currentCamera == FPCAMERA || currentCamera == TPCAMERA) {
		switch(direction) {
			case (int)(Character::left):
				pac.setIncomingDirection((Character::Directions)((int)((pac.getDiscreteDirection() + 3)%4)));
				break;
			case (int)(Character::right):
				pac.setIncomingDirection((Character::Directions)((int)((pac.getDiscreteDirection() + 5)%4)));
				break;
			case (int)(Character::down):
				pac.setIncomingDirection((Character::Directions)((int)((pac.getDiscreteDirection() + 2)%4)));
				break;
		}
	}
}

void Game::restart() {
	pac.setPosition(maze.getPacStart());
	pac.setDiscreteDirection(Character::up);
	for (int i = 0; i < nPhantoms; i++) {
		phantoms[i].setPosition(maze.getPhantomStart()[i]);
	}
}
void Game::gameOver() {
	PlaySound(TEXT("gameover.wav"), NULL, SND_SYNC);
	exit(0);
}
void Game::victory() {
	PlaySound(TEXT("victory.wav"), NULL, SND_SYNC);
	level++;
	pac.setLives(pac.getLives() + 1);
	if (level==2) {
		maze.openMaze("level2.lab");
		resetAboveCamera();
		delete [] phantoms;
		nPhantoms = maze.getNPhantoms();
		pac.setPosition(maze.getPacStart());
		phantoms = new Phantom[nPhantoms]();
		for (int i = 0; i < nPhantoms; i++) {
			phantoms[i].setPosition(maze.getPhantomStart()[i]);
		}
		restart();
	}
	else
		exit(0);
}
void Game::die() {
	PlaySound(TEXT("holyshit.wav"), NULL, SND_SYNC);
	pac.setLives(pac.getLives() -1);
	if (pac.getLives() != 0)
		restart();
	else
		gameOver();
}

void Game::killPhantonSounds(int phantomsAte) {
	static bool virgin =true;
	switch (phantomsAte) {
		case 1:
			if (virgin) {
				PlaySound(TEXT("1_death.wav"), NULL, SND_ASYNC);

				virgin = false;
			}
			else {
				// Som de morte de fantasma
			}
			break;
		case 2:
			PlaySound(TEXT("2_death.wav"), NULL, SND_ASYNC);
			break;
		case 3:
			PlaySound(TEXT("3_death.wav"), NULL, SND_ASYNC);
			break;
		case 4:
			PlaySound(TEXT("4_death.wav"), NULL, SND_ASYNC);
			break;
	}
}

